<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>cannon.js - ragdoll demo</title>
    <link rel="stylesheet" href="css/style.css" type="text/css" />
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
  </head>
  <body>
    <script type="module">
      import * as CANNON from '../dist/cannon-es.js'
      import { Demo } from './js/Demo.js'

      const demo = new Demo()

      demo.addScene('No cone joints', () => {
        const world = setupWorld(demo)

        // Add a sphere to land on
        const sphereBody = createStaticSphere()
        world.addBody(sphereBody)
        demo.addVisual(sphereBody)

        // Create the ragdoll
        // It returns an array of body parts and their constraints
        const { bodies, constraints } = createRagdoll({
          scale: 3,
          angle: Math.PI,
          angleShoulders: Math.PI,
          twistAngle: Math.PI,
        })

        bodies.forEach((body) => {
          // Move the ragdoll up
          const position = new CANNON.Vec3(0, 10, 0)
          body.position.vadd(position, body.position)

          world.addBody(body)
          demo.addVisual(body)
        })

        constraints.forEach((constraint) => {
          world.addConstraint(constraint)
        })
      })

      demo.addScene('Normal cone joints', () => {
        const world = setupWorld(demo)

        // Add a sphere to land on
        const sphereBody = createStaticSphere()
        world.addBody(sphereBody)
        demo.addVisual(sphereBody)

        // Create the ragdoll
        // It returns an array of body parts and their constraints
        const { bodies, constraints } = createRagdoll({
          scale: 3,
          angle: Math.PI / 4,
          angleShoulders: Math.PI / 3,
          twistAngle: Math.PI / 8,
        })

        bodies.forEach((body) => {
          // Move the ragdoll up
          const position = new CANNON.Vec3(0, 10, 0)
          body.position.vadd(position, body.position)

          world.addBody(body)
          demo.addVisual(body)
        })

        constraints.forEach((constraint) => {
          world.addConstraint(constraint)
        })
      })

      demo.addScene('Thin cone joints', () => {
        const world = setupWorld(demo)

        // Add a sphere to land on
        const sphereBody = createStaticSphere()
        world.addBody(sphereBody)
        demo.addVisual(sphereBody)

        // Create the ragdoll
        // It returns an array of body parts and their constraints
        const { bodies, constraints } = createRagdoll({
          scale: 3,
          angle: 0,
          angleShoulders: 0,
          twistAngle: 0,
        })

        bodies.forEach((body) => {
          // Move the ragdoll up
          const position = new CANNON.Vec3(0, 10, 0)
          body.position.vadd(position, body.position)

          world.addBody(body)
          demo.addVisual(body)
        })

        constraints.forEach((constraint) => {
          world.addConstraint(constraint)
        })
      })

      demo.start()

      function createRagdoll({ scale = 1, angle, angleShoulders, twistAngle }) {
        const bodies = []
        const constraints = []

        const shouldersDistance = 0.5 * scale
        const upperArmLength = 0.4 * scale
        const lowerArmLength = 0.4 * scale
        const upperArmSize = 0.2 * scale
        const lowerArmSize = 0.2 * scale
        const neckLength = 0.1 * scale
        const headRadius = 0.25 * scale
        const upperBodyLength = 0.6 * scale
        const pelvisLength = 0.4 * scale
        const upperLegLength = 0.5 * scale
        const upperLegSize = 0.2 * scale
        const lowerLegSize = 0.2 * scale
        const lowerLegLength = 0.5 * scale

        const headShape = new CANNON.Sphere(headRadius)
        const upperArmShape = new CANNON.Box(
          new CANNON.Vec3(upperArmLength * 0.5, upperArmSize * 0.5, upperArmSize * 0.5)
        )
        const lowerArmShape = new CANNON.Box(
          new CANNON.Vec3(lowerArmLength * 0.5, lowerArmSize * 0.5, lowerArmSize * 0.5)
        )
        const upperBodyShape = new CANNON.Box(
          new CANNON.Vec3(shouldersDistance * 0.5, lowerArmSize * 0.5, upperBodyLength * 0.5)
        )
        const pelvisShape = new CANNON.Box(
          new CANNON.Vec3(shouldersDistance * 0.5, lowerArmSize * 0.5, pelvisLength * 0.5)
        )
        const upperLegShape = new CANNON.Box(
          new CANNON.Vec3(upperLegSize * 0.5, lowerArmSize * 0.5, upperLegLength * 0.5)
        )
        const lowerLegShape = new CANNON.Box(
          new CANNON.Vec3(lowerLegSize * 0.5, lowerArmSize * 0.5, lowerLegLength * 0.5)
        )

        // Lower legs
        const lowerLeftLeg = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(shouldersDistance / 2, 0, lowerLegLength / 2),
        })
        const lowerRightLeg = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(-shouldersDistance / 2, 0, lowerLegLength / 2),
        })
        lowerLeftLeg.addShape(lowerLegShape)
        lowerRightLeg.addShape(lowerLegShape)
        bodies.push(lowerLeftLeg)
        bodies.push(lowerRightLeg)

        // Upper legs
        const upperLeftLeg = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(
            shouldersDistance / 2,
            0,
            lowerLeftLeg.position.z + lowerLegLength / 2 + upperLegLength / 2
          ),
        })
        const upperRightLeg = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(
            -shouldersDistance / 2,
            0,
            lowerRightLeg.position.z + lowerLegLength / 2 + upperLegLength / 2
          ),
        })
        upperLeftLeg.addShape(upperLegShape)
        upperRightLeg.addShape(upperLegShape)
        bodies.push(upperLeftLeg)
        bodies.push(upperRightLeg)

        // Pelvis
        const pelvis = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(0, 0, upperLeftLeg.position.z + upperLegLength / 2 + pelvisLength / 2),
        })
        pelvis.addShape(pelvisShape)
        bodies.push(pelvis)

        // Upper body
        const upperBody = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(0, 0, pelvis.position.z + pelvisLength / 2 + upperBodyLength / 2),
        })
        upperBody.addShape(upperBodyShape)
        bodies.push(upperBody)

        // Head
        const head = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(0, 0, upperBody.position.z + upperBodyLength / 2 + headRadius + neckLength),
        })
        head.addShape(headShape)
        bodies.push(head)

        // Upper arms
        const upperLeftArm = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(
            shouldersDistance / 2 + upperArmLength / 2,
            0,
            upperBody.position.z + upperBodyLength / 2
          ),
        })
        const upperRightArm = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(
            -shouldersDistance / 2 - upperArmLength / 2,
            0,
            upperBody.position.z + upperBodyLength / 2
          ),
        })
        upperLeftArm.addShape(upperArmShape)
        upperRightArm.addShape(upperArmShape)
        bodies.push(upperLeftArm)
        bodies.push(upperRightArm)

        // Lower arms
        const lowerLeftArm = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(
            upperLeftArm.position.x + lowerArmLength / 2 + upperArmLength / 2,
            0,
            upperLeftArm.position.z
          ),
        })
        const lowerRightArm = new CANNON.Body({
          mass: 1,
          position: new CANNON.Vec3(
            upperRightArm.position.x - lowerArmLength / 2 - upperArmLength / 2,
            0,
            upperRightArm.position.z
          ),
        })
        lowerLeftArm.addShape(lowerArmShape)
        lowerRightArm.addShape(lowerArmShape)
        bodies.push(lowerLeftArm)
        bodies.push(lowerRightArm)

        // Neck joint
        const neckJoint = new CANNON.ConeTwistConstraint(head, upperBody, {
          pivotA: new CANNON.Vec3(0, 0, -headRadius - neckLength / 2),
          pivotB: new CANNON.Vec3(0, 0, upperBodyLength / 2),
          axisA: CANNON.Vec3.UNIT_Z,
          axisB: CANNON.Vec3.UNIT_Z,
          angle,
          twistAngle,
        })
        constraints.push(neckJoint)

        // Knee joints
        const leftKneeJoint = new CANNON.ConeTwistConstraint(lowerLeftLeg, upperLeftLeg, {
          pivotA: new CANNON.Vec3(0, 0, lowerLegLength / 2),
          pivotB: new CANNON.Vec3(0, 0, -upperLegLength / 2),
          axisA: CANNON.Vec3.UNIT_Z,
          axisB: CANNON.Vec3.UNIT_Z,
          angle,
          twistAngle,
        })
        const rightKneeJoint = new CANNON.ConeTwistConstraint(lowerRightLeg, upperRightLeg, {
          pivotA: new CANNON.Vec3(0, 0, lowerLegLength / 2),
          pivotB: new CANNON.Vec3(0, 0, -upperLegLength / 2),
          axisA: CANNON.Vec3.UNIT_Z,
          axisB: CANNON.Vec3.UNIT_Z,
          angle,
          twistAngle,
        })
        constraints.push(leftKneeJoint)
        constraints.push(rightKneeJoint)

        // Hip joints
        const leftHipJoint = new CANNON.ConeTwistConstraint(upperLeftLeg, pelvis, {
          pivotA: new CANNON.Vec3(0, 0, upperLegLength / 2),
          pivotB: new CANNON.Vec3(shouldersDistance / 2, 0, -pelvisLength / 2),
          axisA: CANNON.Vec3.UNIT_Z,
          axisB: CANNON.Vec3.UNIT_Z,
          angle,
          twistAngle,
        })
        const rightHipJoint = new CANNON.ConeTwistConstraint(upperRightLeg, pelvis, {
          pivotA: new CANNON.Vec3(0, 0, upperLegLength / 2),
          pivotB: new CANNON.Vec3(-shouldersDistance / 2, 0, -pelvisLength / 2),
          axisA: CANNON.Vec3.UNIT_Z,
          axisB: CANNON.Vec3.UNIT_Z,
          angle,
          twistAngle,
        })
        constraints.push(leftHipJoint)
        constraints.push(rightHipJoint)

        // Spine
        const spineJoint = new CANNON.ConeTwistConstraint(pelvis, upperBody, {
          pivotA: new CANNON.Vec3(0, 0, pelvisLength / 2),
          pivotB: new CANNON.Vec3(0, 0, -upperBodyLength / 2),
          axisA: CANNON.Vec3.UNIT_Z,
          axisB: CANNON.Vec3.UNIT_Z,
          angle,
          twistAngle,
        })
        constraints.push(spineJoint)

        // Shoulders
        const leftShoulder = new CANNON.ConeTwistConstraint(upperBody, upperLeftArm, {
          pivotA: new CANNON.Vec3(shouldersDistance / 2, 0, upperBodyLength / 2),
          pivotB: new CANNON.Vec3(-upperArmLength / 2, 0, 0),
          axisA: CANNON.Vec3.UNIT_X,
          axisB: CANNON.Vec3.UNIT_X,
          angle: angleShoulders,
        })
        const rightShoulder = new CANNON.ConeTwistConstraint(upperBody, upperRightArm, {
          pivotA: new CANNON.Vec3(-shouldersDistance / 2, 0, upperBodyLength / 2),
          pivotB: new CANNON.Vec3(upperArmLength / 2, 0, 0),
          axisA: CANNON.Vec3.UNIT_X,
          axisB: CANNON.Vec3.UNIT_X,
          angle: angleShoulders,
          twistAngle,
        })
        constraints.push(leftShoulder)
        constraints.push(rightShoulder)

        // Elbow joint
        const leftElbowJoint = new CANNON.ConeTwistConstraint(lowerLeftArm, upperLeftArm, {
          pivotA: new CANNON.Vec3(-lowerArmLength / 2, 0, 0),
          pivotB: new CANNON.Vec3(upperArmLength / 2, 0, 0),
          axisA: CANNON.Vec3.UNIT_X,
          axisB: CANNON.Vec3.UNIT_X,
          angle,
          twistAngle,
        })
        const rightElbowJoint = new CANNON.ConeTwistConstraint(lowerRightArm, upperRightArm, {
          pivotA: new CANNON.Vec3(lowerArmLength / 2, 0, 0),
          pivotB: new CANNON.Vec3(-upperArmLength / 2, 0, 0),
          axisA: CANNON.Vec3.UNIT_X,
          axisB: CANNON.Vec3.UNIT_X,
          angle,
          twistAngle,
        })
        constraints.push(leftElbowJoint)
        constraints.push(rightElbowJoint)

        return { bodies, constraints }
      }

      function setupWorld(demo) {
        const world = demo.getWorld()
        world.gravity.set(0, -5, 0)

        // Static ground plane
        const groundShape = new CANNON.Plane()
        const groundBody = new CANNON.Body({ mass: 0 })
        groundBody.addShape(groundShape)
        groundBody.position.set(0, -1, 0)
        groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
        world.addBody(groundBody)
        demo.addVisual(groundBody)

        return world
      }

      function createStaticSphere() {
        const sphereShape = new CANNON.Sphere(4)
        const sphereBody = new CANNON.Body({ mass: 0 })
        sphereBody.addShape(sphereShape)
        sphereBody.position.set(0, -1, 0)
        return sphereBody
      }
    </script>
  </body>
</html>
